package me.itsoo.artemis.framework.core.generator;

import me.itsoo.artemis.framework.core.util.AddressUtils;
import me.itsoo.artemis.framework.core.util.StringUtils;

/**
 * 雪花 ID 生成器
 *
 * @author zxy
 * @version 0.0.1
 * @since 2023/5/30
 */
public class SnowflakeKeyGenerator implements KeyGenerator<Long> {

    private static final long START_TIMESTAMP = 1584843269315L;

    private static final long MAX_SEQUENCE = 4095L;

    private static final long MAX_DATA_CENTER_NUM = 31L;

    private static final long MAX_MACHINE_NUM = MAX_DATA_CENTER_NUM;

    private static final long DATA_CENTER_LEFT = 17L;

    private static final long MACHINE_LEFT = 12L;

    private static final long TIMESTAMP_LEFT = 22L;

    private final long dataCenterId;

    private final long machineId;

    private long sequence;

    private static long lastTimestamp = -1L;

    private SnowflakeKeyGenerator(long dataCenterId, long machineId) {
        final String messageTemplate = "{} can't be greater than {} or less than 0";
        if (dataCenterId > MAX_DATA_CENTER_NUM || dataCenterId < 0L) {
            throw new IllegalArgumentException(StringUtils.format(messageTemplate, "dataCenterId", MAX_DATA_CENTER_NUM));
        }

        if (machineId > MAX_MACHINE_NUM || machineId < 0L) {
            throw new IllegalArgumentException(StringUtils.format(messageTemplate, "machineId", MAX_MACHINE_NUM));
        }

        this.dataCenterId = dataCenterId;
        this.machineId = machineId;
    }

    /**
     * 创建实例
     *
     * @param dataCenterId long
     * @return SnowflakeKeyGenerator
     */
    public static SnowflakeKeyGenerator create(long dataCenterId) {
        return create(dataCenterId, AddressUtils.getLocalIp4AddressMod32());
    }

    /**
     * 创建实例
     *
     * @param dataCenterId long
     * @param machineId    long
     * @return SnowflakeKeyGenerator
     */
    public static SnowflakeKeyGenerator create(long dataCenterId, long machineId) {
        return new SnowflakeKeyGenerator(dataCenterId, machineId);
    }

    @Override
    public synchronized Long next() {
        long currTimestamp = getNewTimestamp();
        if (currTimestamp < lastTimestamp) {
            throw new RuntimeException("Clock moved backwards. Refusing to generate id");
        }

        if (currTimestamp == lastTimestamp) {
            this.sequence = this.sequence + 1L & MAX_SEQUENCE;
            if (this.sequence == 0L) {
                currTimestamp = getNextMill();
            }
        } else {
            this.sequence = 0L;
        }

        lastTimestamp = currTimestamp;
        return currTimestamp - START_TIMESTAMP << TIMESTAMP_LEFT | this.dataCenterId << DATA_CENTER_LEFT | this.machineId << MACHINE_LEFT | this.sequence;
    }

    private static long getNewTimestamp() {
        return System.currentTimeMillis();
    }

    private static long getNextMill() {
        long mill;
        for (mill = getNewTimestamp(); mill <= lastTimestamp; mill = getNewTimestamp()) {
        }
        return mill;
    }
}
